package gecp.edge.image;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * SubImage
 *
 * @author Cosmin
 */
public class SubImage {

    /**
     * The color matrix of the image
     */
    public int[][] color;
    /**
     * The image matrix width
     */
    public int width;
    /**
     * The image matrix height
     */
    public int height;
    /**
     * The offset on x from the color matrix
     */
    public int offsetX;
    /**
     * The offset on y from the color matrix
     */
    public int offsetY;
    /**
     * If the matrix of color was build successfully then this is true or false
     * if it wasn't build
     */
    protected boolean wasBuild;
    /**
     * The matrix of force amplitudes
     */
    public double[][] forceAmplitude;
    /**
     * The matrix of force angles
     */
    public double[][] forceAngle;
    /**
     * The compute width
     */
    public int computeWidth;
    /**
     * The compute height
     */
    public int computeHeight;
    /**
     * The compute offset on x axis
     */
    public int computeOffsetX;
    /**
     * The compute offset on y axis
     */
    public int computeOffsetY;
    /**
     * The user current step
     */
    protected int _step;
    /**
     * The user current thresholder
     */
    protected double _thresholder;
    /**
     * The current step size build from the user step
     */
    protected int _stepSize;
    /**
     * The current step count build from the user step
     */
    protected int _stepCount;

    /**
     * Creating a sub image color matrix
     *
     * @param width The sub image width
     * @param height The sub image height
     * @param offsetX The sub image offset on x axis
     * @param offsetY The sub image offset on y axis
     */
    public SubImage(int width, int height, int offsetX, int offsetY) {
        this.color = new int[width][height];
        this.width = width;
        this.height = height;
        this.offsetX = offsetX;
        this.offsetY = offsetY;
        this.wasBuild = false;
    }

    /**
     * Build a sub image color matrix from a image matrix
     *
     * @param image The image color matrix
     *
     * @throws Exception When the offset and size from constructor are
     * invalidated by the image color matrix
     */
    public void buildSubImage(int[][] image) throws Exception {
        for (int i = 0; i < this.width; i++) {
            if (i + this.offsetX >= image.length) {
                throw new Exception("Invalid image");
            }
            int x = i + this.offsetX;
            for (int j = 0; j < this.height; j++) {
                if (j + this.offsetY >= image[x].length) {
                    throw new Exception("Invalid image");
                }
                int y = j + this.offsetY;
                this.color[i][j] = image[x][y];
            }
        }
        this.wasBuild = true;
    }

    /**
     * Compute the forces for each point and the sum of that forces in the
     * amplitude matrix and the angles matrix. The compute offset is the offset
     * from 0, 0 of current color matrix to the real pixel where we start the
     * computation. The compute width and height are the size of the real size
     * of the matrix for who we compute the forces.
     *
     * @param computeWidth The compute width
     * @param computeHeight The compute height
     * @param computeOffsetX The compute offset on x axis
     * @param computeOffsetY The compute offset on y axis
     *
     * @throws Exception When the color matrix wasn't build successfully
     * @throws Exception When the compute width is invalid
     * @throws Exception When the compute width and compute offset x is invalid
     * @throws Exception When the compute height is invalid
     * @throws Exception When the compute height and compute offset y is invalid
     */
    public void computeSubImage(int computeWidth, int computeHeight, int computeOffsetX, int computeOffsetY, int step, double thresholder) throws Exception {
        if (!this.wasBuild) {
            throw new Exception("Color matrix wasn't build or was build with errors");
        }
        if (this.width < this.computeWidth) {
            throw new Exception("Invalid compute width given");
        }
        if (this.width < this.computeWidth + this.computeOffsetX) {
            throw new Exception("Invalid compute width and compute offset x given");
        }
        if (this.height < this.computeHeight) {
            throw new Exception("Invalid compute height given");
        }
        if (this.height < this.computeHeight + this.computeOffsetY) {
            throw new Exception("Invalid compute height and compute offset y given");
        }
        this._step = step;
        this._thresholder = thresholder;
        this._stepSize = Image.getStepSize(step);
        this._stepCount = Image.getStepCount(step);
        this.computeWidth = computeWidth;
        this.computeHeight = computeHeight;
        this.computeOffsetX = computeOffsetX;
        this.computeOffsetY = computeOffsetY;
        this.forceAmplitude = new double[computeWidth][computeHeight];
        this.forceAngle = new double[computeWidth][computeHeight];
        for (int i = 0; i < this.computeWidth; i++) {
            long start_time = System.currentTimeMillis();
            int x = i + this.computeOffsetX;
            for (int j = 0; j < this.computeHeight; j++) {
                int y = j + this.computeOffsetY;
                try {
                    this._proccessPixelOneStep(x, y);
                } catch (Exception ex) {
                    Logger.getLogger(SubImage.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            long end_time = System.currentTimeMillis();
            long time = end_time - start_time;
            System.out.println("Column " + x + " / " + this.width + " done in " + time + " ms.");
        }
    }

    /**
     * Compute all the forces for an pixel and store the sum of that forces into
     * forceAmplitude and forceAngle arrays
     *
     * @param x The coordinate on x axis of the pixel
     * @param y The coordinate on y axis of the pixel
     *
     * @throws Exception When the color matrix wasn't build successfully
     */
    protected void _proccessPixel(int x, int y) throws Exception {
        if (!this.wasBuild) {
            throw new Exception("Color matrix wasn't build or was build with errors");
        }
        List<Force> forces = new ArrayList<Force>();
        for (int count = 1; count <= this._stepSize; count += this._stepCount) {
            int _width = count * 2;
            int _x = x - count;
            int _y = y - count;
            int mx;
            int my;
            for (int i = 0; i < 4; i++) {
                switch (i) {
                    case 1:
                        mx = 0;
                        my = 1;
                        break;
                    case 2:
                        mx = -1;
                        my = 0;
                        break;
                    case 3:
                        mx = 0;
                        my = -1;
                        break;
                    default:
                        mx = 1;
                        my = 0;
                }
                for (int j = 0; j < _width; j++) {
                    if (_x >= 0 && _y >= 0 && _x < this.width && _y < this.height) {
                        try {
                            Force f = new Force(this._getColorDistance(x, y, _x, _y), x, y, _x, _y);
                            forces.add(f);
                        } catch (Exception ex) {
                            Logger.getLogger(SubImage.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                    _x += mx;
                    _y += my;
                }
            }
        }
        /**
         * Computing the sum of all forces
         */
        if (null != forces && !forces.isEmpty()) {
            double mx = 0;
            double my = 0;
            for (Force force : forces) {
                force.computeMovement();
                mx += force.getMoveX();
                my += force.getMoveY();
            }
            double amp = Math.sqrt(mx * mx + my * my);
            mx += x;
            my += y;
            Force sum = new Force(amp, x, y, (int) mx, (int) my);
            this.forceAmplitude[x][y] = sum.getAmplitude();
            this.forceAngle[x][y] = sum.getAngleDegree();
            forces.clear();
        }
    }

    /**
     * Compute all the forces for an pixel and store the sum of that forces into
     * forceAmplitude and forceAngle arrays
     *
     * @param x The coordinate on x axis of the pixel
     * @param y The coordinate on y axis of the pixel
     *
     * @throws Exception When the color matrix wasn't build successfully
     */
    protected void _proccessPixelOneStep(int x, int y) throws Exception {
        if (!this.wasBuild) {
            throw new Exception("Color matrix wasn't build or was build with errors");
        }
        double fx = 0;
        double fy = 0;
        for (int count = 1; count <= this._stepSize; count += this._stepCount) {
            int _width = count * 2;
            int _x = x - count;
            int _y = y - count;
            int mx;
            int my;
            for (int i = 0; i < 4; i++) {
                switch (i) {
                    case 1:
                        mx = 0;
                        my = 1;
                        break;
                    case 2:
                        mx = -1;
                        my = 0;
                        break;
                    case 3:
                        mx = 0;
                        my = -1;
                        break;
                    default:
                        mx = 1;
                        my = 0;
                }
                for (int j = 0; j < _width; j++) {
                    if (_x >= 0 && _y >= 0 && _x < this.width && _y < this.height) {
                        try {
                            Force f = new Force(this._getColorDistance(x, y, _x, _y), x, y, _x, _y);
                            f.computeMovement();
                            fx += f.getMoveX();
                            fy += f.getMoveY();
                        } catch (Exception ex) {
                            Logger.getLogger(SubImage.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                    _x += mx;
                    _y += my;
                }
            }
        }
        /**
         * Computing the sum of all forces
         */
        double amp = Math.sqrt(fx * fx + fy * fy);
        fx += x;
        fy += y;
        Force sum = new Force(amp, x, y, (int) fx, (int) fy);
        this.forceAmplitude[x][y] = sum.getAmplitude();
        this.forceAngle[x][y] = sum.getAngleDegree();
    }

    /**
     * Compute the color distance between to pixels
     *
     * @param x0 The first pixel coordinate on x axis
     * @param y0 The first pixel coordinate on y axis
     * @param x1 The second pixel coordinate on x axis
     * @param y1 The second pixel coordinate on y axis
     *
     * @return The color distance
     */
    protected double _getColorDistance(int x0, int y0, int x1, int y1) {
        return Math.abs(this.color[x0][y0] - this.color[x1][y1]);
    }
}
